package org.springframework.social.connect.support;

import org.springframework.social.connect.ApiAdapter;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionData;
import org.springframework.social.connect.ConnectionKey;
import org.springframework.social.connect.ConnectionValues;
import org.springframework.social.connect.UserProfile;

public abstract class AbstractConnection<A> implements Connection<A> {
  private static final long serialVersionUID = 7330875324290049412L;
  
  private final transient ApiAdapter<A> apiAdapter;
  
  private ConnectionKey key;
  
  private String providerUnionId;
  
  private String displayName;
  
  private String profileUrl;
  
  private String imageUrl;
  
  private boolean valuesInitialized;
  
  private final Monitor monitor = new Monitor();
  
  public AbstractConnection(ApiAdapter<A> apiAdapter) {
    this.apiAdapter = apiAdapter;
  }
  
  public AbstractConnection(ConnectionData data, ApiAdapter<A> apiAdapter) {
    this.key = new ConnectionKey(data.getProviderId(), data.getProviderUserId());
    this.apiAdapter = apiAdapter;
    this.providerUnionId = data.getProviderUnionId();
    this.displayName = data.getDisplayName();
    this.profileUrl = data.getProfileUrl();
    this.imageUrl = data.getImageUrl();
    this.valuesInitialized = true;
  }
  
  public ConnectionKey getKey() {
    return this.key;
  }
  
  public String getProviderUnionId() {
    if (this.monitor == null) {
      return this.providerUnionId;
    }
    synchronized (this.monitor) {
      initValues();
      return this.providerUnionId;
    } 
  }
  
  public String getDisplayName() {
    if (this.monitor == null) {
      return this.displayName;
    }
    synchronized (this.monitor) {
      initValues();
      return this.displayName;
    } 
  }
  
  public String getProfileUrl() {
    if (this.monitor == null) {
      return this.profileUrl;
    }
    synchronized (this.monitor) {
      initValues();
      return this.profileUrl;
    }
  }
  
  public String getImageUrl() {
    if (this.monitor == null) {
      return this.imageUrl;
    }
    synchronized (this.monitor) {
      initValues();
      return this.imageUrl;
    } 
  }
  
  public boolean test() {
    return this.apiAdapter.test(getApi());
  }
  
  public boolean hasExpired() {
    return false;
  }
  
  public void refresh() {}
  
  public UserProfile fetchUserProfile() {
    return this.apiAdapter.fetchUserProfile(getApi());
  }
  
  public void updateStatus(String message) {
    this.apiAdapter.updateStatus(getApi(), message);
  }
  
  public void sync() {
    synchronized (this.monitor) {
      setValues();
    } 
  }
  
  protected void initKey(String providerId, String providerUserId) {
    if (providerUserId == null) {
      providerUserId = (setValues()).providerUserId;
    }
    this.key = new ConnectionKey(providerId, providerUserId);
  }
  
  protected Object getMonitor() {
    return this.monitor;
  }
  
  @Override
  public boolean equals(Object o) {
    if (!(o instanceof Connection)) {
      return false;
    }
    Connection other = (Connection)o;
    return this.key.equals(other.getKey());
  }
  
  @Override
  public int hashCode() {
    return this.key.hashCode();
  }
  
  private void initValues() {
    if (!this.valuesInitialized) {
      setValues();
    }
  }
  
  private ServiceProviderConnectionValuesImpl setValues() {
    ServiceProviderConnectionValuesImpl values = new ServiceProviderConnectionValuesImpl();
    this.apiAdapter.setConnectionValues(getApi(), values);
    this.valuesInitialized = true;
    return values;
  }
  
  @Override
  public abstract A getApi();
  
  @Override
  public abstract ConnectionData createData();
  
  private class ServiceProviderConnectionValuesImpl implements ConnectionValues {
    private String providerUserId;
    
    private ServiceProviderConnectionValuesImpl() {}
    
    @Override
    public void setProviderUserId(String providerUserId) {
      this.providerUserId = providerUserId;
    }
    
    @Override
    public void setProviderUnionId(String providerUnionId) {
      AbstractConnection.this.providerUnionId = providerUnionId;
    }
    
    @Override
    public void setDisplayName(String displayName) {
      AbstractConnection.this.displayName = displayName;
    }
    
    @Override
    public void setProfileUrl(String profileUrl) {
      AbstractConnection.this.profileUrl = profileUrl;
    }
    
    @Override
    public void setImageUrl(String imageUrl) {
      AbstractConnection.this.imageUrl = imageUrl;
    }
  }
}
